//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Validation Application Block
//===============================================================================
// Code file original author: Martin Lapierre, http://devinstinct.com
//===============================================================================

using System;
using System.Security.Permissions;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using EntLibContrib.Validation.Validators;
using EntLibContrib.Validation.Properties;

namespace EntLibContrib.Validation
{
	/// <summary>
	/// The exception that is thrown when the value of one 
	/// of the arguments provided to a method is not valid.
	/// </summary>
	[Serializable]
	public class ArgumentValidationException : ArgumentException
	{
		#region Constants
		private const string isSafeName = "isSafe";
		private const string parameterStringName = "paramString";
		private const string validationMessagesName = "validationMessages";
		#endregion

		#region Fields
		/// <summary>
		/// The parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Not serialized accross application domains.
		/// </remarks>
		[NonSerialized]
		private object parameter;

		/// <summary>
		/// The string value of the parameter that causes this exception.
		/// </summary>
		private string parameterString;

		/// <summary>
		/// The validation results of the parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Not serialized accross application domains.
		/// </remarks>
		[NonSerialized]
		private ValidationResults validationResults;

		/// <summary>
		/// The validation messages of the parameter that causes this exception.
		/// </summary>
		private string[] validationMessages;

		/// <summary>
		/// Indicates if the exception is safe; that is, if it 
		/// contains the parameter value (unsafe) or not (safe).
		/// </summary>
		private bool isSafe = true;
		#endregion Fields

		#region Properties
		/// <summary>
		/// The parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Can be null.
		/// Not serialized.
		/// </remarks>
		public object Parameter
		{
			get { return this.parameter; }
		}

		/// <summary>
		/// The string value of the parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Can be null.
		/// </remarks>
		public string ParameterString
		{
			get { return this.parameterString; }
		}

		/// <summary>
		/// The validation results of the parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Can be null.
		/// Not serialized.
		/// </remarks>
		public ValidationResults ValidationResults
		{
			get { return this.validationResults; }
		}

		/// <summary>
		/// The validation messages of the parameter that causes this exception.
		/// </summary>
		/// <remarks>
		/// Can be null.
		/// </remarks>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
		public string[] ValidationMessages
		{
			get { return this.validationMessages; }
		}

		/// <summary>
		/// Gets a value which indicates if the exception is safe; that is, 
		/// if it contains the parameter value (unsafe) or not (safe).
		/// </summary>
		public bool IsSafe
		{
			get { return this.isSafe; }
		}

		/// <summary>
		/// Gets the error message, including the parameter 
		/// name and the validation messages, if available.
		/// </summary>
		/// <remarks>
		/// Can't be null.
		/// </remarks>
		public override string Message
		{
			get
			{
				StringBuilder message = new StringBuilder(base.Message);

				if (!this.isSafe)
				{
					message.Append(Environment.NewLine);
					message.AppendFormat(Resources.ArgumentValidationExceptionParameterValue, this.parameterString ?? "(null)");
				}

				if (this.validationMessages != null)
					foreach (string validationMessage in this.validationMessages)
					{
						message.Append(Environment.NewLine);
						message.Append(validationMessage);
					}

				return message.ToString();
			}
		}
		#endregion

		#region Construction
		/// <summary>
		/// Standard default constructor.
		/// </summary>
		public ArgumentValidationException()
		{
		}

		/// <summary>
		/// Standard constructor.
		/// </summary>
		/// <param name="message">The error message that explains the reason for the exception.</param>
		public ArgumentValidationException(string message)
			: base(message)
		{
		}

		/// <summary>
		/// Standard constructor.
		/// </summary>
		/// <param name="message">The error message that explains the reason for the exception.</param>
		/// <param name="innerException">
		/// The exception that is the cause of the current exception. If the innerException
		/// parameter is not a null reference, the current exception is raised in a catch
		/// block that handles the inner exception.
		///</param>
		public ArgumentValidationException(string message, Exception innerException)
			: base(message, innerException)
		{
		}

		/// <summary>
		/// Standard constructor.
		/// </summary>
		/// <param name="info">The object that holds the serialized object data.</param>
		/// <param name="context">The contextual information about the source or destination.</param>
		protected ArgumentValidationException(SerializationInfo info, StreamingContext context)
			: base(info, context)
		{
			// Validate pre-conditions.
			// Assumed to be validated by the base class constructor.

			this.isSafe = info.GetBoolean(isSafeName);
			if (!this.isSafe)
				this.parameterString = info.GetString(parameterStringName);
			this.validationMessages = (string[])info.GetValue(validationMessagesName, typeof(string[]));
		}

		/// <summary>
		/// This constructor creates a layer-safe exception that doesn't 
		/// propagate the parameter value and validation results data.
		/// </summary>
		/// <param name="message">The error message that explains the reason for the exception.</param>
		/// <param name="parameterName">The name of the parameter that causes this exception.</param>
		/// <param name="validationResults">The validation results of the parameter that causes this exception.</param>
		public ArgumentValidationException(string message, string parameterName, ValidationResults validationResults)
			: base(message, parameterName)
		{
			// Validate pre-conditions.
			// "message" can be null.
			ArgumentValidation.Validate("parameterName", parameterName, DefaultValidators.NotNullValidator);
			ArgumentValidation.Validate("validationResults", validationResults, DefaultValidators.NotNullValidator);

			InitValidationMessages(validationResults);
		}

		/// <summary>
		/// This constructor creates a layer-unsafe exception that
		/// propagates the parameter value and validation results data.
		/// </summary>
		/// <param name="message">The error message that explains the reason for the exception.</param>
		/// <param name="parameterName">The name of the parameter that causes this exception.</param>
		/// <param name="parameter">The parameter that causes this exception.</param>
		/// <param name="validationResults">The validation results of the parameter that causes this exception.</param>
		public ArgumentValidationException(string message, string parameterName, object parameter, ValidationResults validationResults)
			: base(message, parameterName)
		{
			// Validate pre-conditions.
			// "message" can be null.
			// "parameter" can be null.
			ArgumentValidation.Validate("parameterName", parameterName, DefaultValidators.NotNullValidator);
			ArgumentValidation.Validate("validationResults", validationResults, DefaultValidators.NotNullValidator);

			this.isSafe = false;
			this.parameter = parameter;
			this.parameterString = parameter == null ? null : parameter.ToString();
			this.validationResults = validationResults;
			InitValidationMessages(validationResults);
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Sets the <see cref="SerializationInfo"/> object with
		/// the parameter name and additional exception information.
		/// </summary>
		/// <param name="info">The object that holds the serialized object data.</param>
		/// <param name="context">The contextual information about the source or destination.</param>
		/// <exception cref="T:System.ArgumentNullException">
		/// The <paramref name="info"/> object is a null reference (Nothing in Visual Basic).
		/// </exception>
		/// <PermissionSet>
		/// 	<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/>
		/// </PermissionSet>
		[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
		public override void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			// Validate pre-conditions.
			// Assumed to be validated by call to base class.
			base.GetObjectData(info, context);

			info.AddValue(isSafeName, this.isSafe);
			if (!this.isSafe)
				info.AddValue(parameterStringName, this.parameterString);
			info.AddValue(validationMessagesName, this.validationMessages);
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Initializes the validation messages from the validation results.
		/// </summary>
		/// <param name="parameterValidationResults">The validation results of the parameter that causes this exception.</param>
		private void InitValidationMessages(ValidationResults parameterValidationResults)
		{
			// Validate pre-conditions.
			// Internal class call: arguments assumed to be valid.
			List<string> resultMessages = new List<string>();
			foreach (ValidationResult result in parameterValidationResults)
				resultMessages.Add(result.Message);
			this.validationMessages = resultMessages.ToArray();
		}
		#endregion
	}
}
